Partial Common Ownership by another name: SALSA (Self-Assessed Licenses Sold at Auction), COST (Common Ownership Self-assessed Tax), Harberger taxes, or depreciating licenses.
從ERC721+ lease(ILease + Taxation(ITaxation, Valuation, ERC721, Remittance, Beneficiary))來
以下探討各Interface為主
interface ILease {
/// @notice 接管某個代幣的租約。當前擁有者會根據當前估值收到款項,且訊息中包含的所有超額值都會被加到押金中。
/// @param tokenId_ 買家希望購買的代幣ID。
/// @param newValuation_ 買家對代幣的新估值。必須大於或等於當前估值。
/// @param currentValuation_ 為了防止前置攻擊,必須提供當前的估值。
/// 買家只會在同意的估值下完成接管。這可以防止惡意的第二個買家在第一筆交易完成之前購買代幣,更改估值,
/// 並吞噬第一個買家的押金。
function takeoverLease(
uint256 tokenId_,
uint256 newValuation_,
uint256 currentValuation_
) external payable;
/// @notice 允許擁有者自我評估代幣的價值。
/// @param tokenId_ 代幣ID。
/// @param newValuation_ 以Wei為單位的新估值。
function selfAssess(uint256 tokenId_, uint256 newValuation_) external;
}
interface ITaxation {
//////////////////////////////
/// 外部方法
//////////////////////////////
/// 允許擁有者提取他們存款的一部分。
function withdrawDeposit(uint256 tokenId_, uint256 wei_) external;
/// 允許擁有者提取他們的全部存款。
function exit(uint256 tokenId_) external;
/// 以`msg.value` Wei增加擁有者的存款。
function deposit(uint256 tokenId_) external payable;
//////////////////////////////
/// 稅收查詢方法
//////////////////////////////
///返回自代幣上次轉移以來收集的稅金數量。
function taxCollectedSinceLastTransferOf(uint256 tokenId_)
external
view
returns (uint256);
///獲取給定代幣的稅率
function taxRateOf(uint256 tokenId_) external view returns (uint256);
///獲取給定代幣的稅收周期
function collectionFrequencyOf(uint256 tokenId_)
external
view
returns (uint256);
/// 定現在和過去某一時間之間累積的應稅金額。
/// @param tokenId_ 要查詢的代幣ID。
/// @param time_ Unix時間戳。
/// @return taxDue 以Wei為單位的應稅金額。
function taxOwedSince(uint256 tokenId_, uint256 time_)
external
view
returns (uint256 taxDue);
/// 公開方法查詢應稅金額。返回當前時間。用於計算預期的稅收義務。
/// @param tokenId_ 要查詢的代幣ID。
/// @return amount 以Wei為單位的應稅金額。
/// @return timestamp 現在的Unix時間戳。
function taxOwed(uint256 tokenId_)
external
view
returns (uint256 amount, uint256 timestamp);
/// 最後一次收稅時間的查詢方法。
/// @param tokenId_ 要查詢的代幣ID。
/// @return Unix時間戳。
function lastCollectionTimeOf(uint256 tokenId_)
external
view
returns (uint256);
//////////////////////////////
/// 存款查詢方法
//////////////////////////////
/// 獲取給定代幣ID的當前存款。
function depositOf(uint256 tokenId_) external view returns (uint256);
/// 可提取的存款金額,即任何存款金額大於應稅金額的部分。
function withdrawableDeposit(uint256 tokenId_)
external
view
returns (uint256);
//////////////////////////////
/// 拍賣查詢
//////////////////////////////
/// 應稅金額是否超過存款?如果是,代幣應該被合約“拍賣”。估值應為零,任何人都可以以瓦斯費用接管代幣的租約。
/// @dev 當估值應為零時,這是一個有用的輔助函數,但合約還沒有反映出來,因為還沒有調用`#_forecloseIfNecessary`。@param tokenId_ 請求拍賣狀態的代幣ID。
/// @return 返回一個布林值,表示合約是否已被拍賣。
function foreclosed(uint256 tokenId_) external view returns (bool);
/// 確定代幣擁有者直到拍賣還有多長時間。
function foreclosureTime(uint256 tokenId_) external view returns (uint256);
}
重要功能
function collectTax(uint256 tokenId_) public {
uint256 valuation = valuationOf(tokenId_);
// There's no tax to be collected on an unvalued token.
if (valuation == 0) return;
// If valuation > 0, contract has not foreclosed.
uint256 owed = _taxOwed(tokenId_);
// Owed will be 0 when the token is owned by its beneficiary.
// i.e. no tax is owed.
if (owed == 0) return;
// If foreclosure should have occured in the past, last collection time will be
// backdated to when the tax was last paid for.
if (foreclosed(tokenId_)) {
_setLastCollectionTime(tokenId_, _backdatedForeclosureTime(tokenId_));
// Set remaining deposit to be collected.
owed = depositOf(tokenId_);
} else {
_setLastCollectionTime(tokenId_, block.timestamp);
}
// Normal collection
_setDeposit(tokenId_, depositOf(tokenId_) - owed);
taxationCollected[tokenId_] += owed;
_setTaxCollectedSinceLastTransfer(
tokenId_,
taxCollectedSinceLastTransferOf(tokenId_) + owed
);
emit LogCollection(tokenId_, owed);
/// Remit taxation to beneficiary.
_remit(beneficiaryOf(tokenId_), owed, RemittanceTriggers.TaxCollection);
_forecloseIfNecessary(tokenId_);
}
interface IRemittance {
/// @notice 允許先前的擁有者提取未成功發送的匯款。
/// @dev 為了降低複雜性,提取的資金與當前的押金完全分開。
function withdrawOutstandingRemittance() external;
}
interface IValuation {
/// @notice 返回代幣的自我評估估值。
/// @param tokenId_ 代幣ID。
/// @return 以Wei為單位的估值。如果代幣沒有設定估值,則返回0。
function valuationOf(uint256 tokenId_) external view returns (uint256);
}
interface IBeneficiary {
/// @notice 為給定的代幣設定受益人。
/// @dev 只應由受益人呼叫。
/// @param tokenId_ 要設定受益人的代幣。
/// @param beneficiary_ 受益人的地址。
function setBeneficiary(uint256 tokenId_, address payable beneficiary_)
external;
/// @notice 獲取給定代幣的受益人
/// @param tokenId_ 要查詢的代幣ID。
/// @return 受益人地址
function beneficiaryOf(uint256 tokenId_) external view returns (address);
}
僅有兩個內部function: _mint() and _burn()整理上述
/// @notice Mints a new token.
/// @param tokenId_ Token's ID.
/// @param leasee_ Token's leasee.
/// @param deposit_ Token's deposit.
/// @param valuation_ Leasee's self assessed valuation of the token.
/// @param beneficiary_ Beneficiary of the token's taxation.
/// @param taxRate_ Tax rate (numerator).
/// @param collectionFrequency_ Tax collection frequency.
function _mint(
uint256 tokenId_,
address leasee_,
uint256 deposit_,
uint256 valuation_,
address payable beneficiary_,
uint256 taxRate_,
uint256 collectionFrequency_
) internal {
_safeMint(leasee_, tokenId_);
_setDeposit(tokenId_, deposit_);
_setValuation(tokenId_, valuation_);
_setBeneficiary(tokenId_, beneficiary_);
_setTaxRate(tokenId_, taxRate_);
_setCollectionFrequency(tokenId_, collectionFrequency_);
}
/// @notice Burns a token.
/// @param tokenId_ ID of token to burn.
function _burn(uint256 tokenId_) internal override _collectTax(tokenId_) {
// Return the current owner's deposit.
_withdrawDeposit(tokenId_, depositOf(tokenId_));
// Burn token
ERC721._burn(tokenId_);
// Delete state
delete _beneficiaries[tokenId_];
delete _valuations[tokenId_];
delete _taxNumerators[tokenId_];
delete _collectionFrequencies[tokenId_];
delete _locked[tokenId_];
}
}
最終在這裡實現,兩個重要功能
/// 接管給定的代幣,創建一個符合部分共同所有權的"包裝"版本。新代幣將返回給擁有者。
/// @dev 注意`#safeTransferFrom`首先要求合約地址被`msg.sender`批准。
/// @param tokenContractAddress_ 發行代幣的合約地址。
/// @param tokenId_ 要被包裝的代幣ID。
/// @param valuation_ 代幣的自我評估估值。
/// @param beneficiary_ 參見`PCO._beneficiaries`。
/// @param taxRate_ 參見`PCO._taxNumerators`。
/// @param collectionFrequency_ 參見`PCO._taxPeriods`。
function wrap(
address tokenContractAddress_,
uint256 tokenId_,
uint256 valuation_,
address payable beneficiary_,
uint256 taxRate_,
uint256 collectionFrequency_
) public payable {
require(valuation_ > 0, "估值必須大於0");
require(beneficiary_ != address(0), "受益人地址不能為零");
require(taxRate_ > 0, "稅率必須大於0");
require(collectionFrequency_ > 0, "稅收頻率必須大於0");
IERC721 tokenContract = IERC721(tokenContractAddress_);
if (msg.sender == beneficiary_) {
// 如果發送者是受益人,確保他們沒有發送存款
require(msg.value == 0, "不需要存款");
} else {
require(msg.value > 0, "需要存款");
}
// 將代幣的所有權轉移給此合約。
tokenContract.safeTransferFrom(msg.sender, address(this), tokenId_);
uint256 _wrappedTokenId = wrappedTokenId(tokenContractAddress_, tokenId_);
_wrappedTokenMap[_wrappedTokenId] = WrappedToken({
contractAddress: tokenContractAddress_,
tokenId: tokenId_,
operatorAddress: msg.sender
});
_mint(
_wrappedTokenId,
msg.sender,
msg.value,
valuation_,
beneficiary_,
taxRate_,
collectionFrequency_
);
emit LogTokenWrapped(tokenContractAddress_, tokenId_, _wrappedTokenId);
}
///@notice 解除包裝給定的代幣。只能由最初包裝代幣的地址調用。銷毀包裝的代幣並將基礎代幣轉移到包裝代幣的最後一個擁有者。
/// @param tokenId_ 包裝代幣的ID。
function unwrap(uint256 tokenId_) public _tokenMinted(tokenId_) {
WrappedToken memory token = _wrappedTokenMap[tokenId_];
require(token.operatorAddress == msg.sender, "只有包裝的原始創建者可以操作");
// 在燃燒之前獲取當前擁有者的地址。
address owner = ownerOf(tokenId_);
// 刪除包裝狀態
delete _wrappedTokenMap[tokenId_];
// 燃燒代幣
_burn(tokenId_);
// 將基礎代幣的所有權轉移到當前擁有者
IERC721 tokenContract = IERC721(token.contractAddress);
tokenContract.safeTransferFrom(address(this), owner, token.tokenId);
}
/// @notice 查詢包裝代幣的URI。
/// @param tokenId_ 參見IERC721
/// @return 代幣URI字符串。
function tokenURI(uint256 tokenId_) public view returns (string memory) {
require(
_exists(tokenId_),
"ERC721Metadata: 非存在代幣的URI查詢"
);
WrappedToken memory wrappedToken = _wrappedTokenMap[tokenId_];
IERC721Metadata metadata = IERC721Metadata(wrappedToken.contractAddress);
return metadata.tokenURI(wrappedToken.tokenId);
}
深入研究可參考這邊
721labs協助的 https://partialcommonownership.com/